Skip to content

execution/cache, db/state/execctx, execution/exec: applied-progress watermark for stale-snapshot cache fills - #22357

Closed
yperbasis wants to merge 1 commit into
yperbasis/statecache-review-fixesfrom
yperbasis/statecache-fill-watermark
Closed

execution/cache, db/state/execctx, execution/exec: applied-progress watermark for stale-snapshot cache fills#22357
yperbasis wants to merge 1 commit into
yperbasis/statecache-review-fixesfrom
yperbasis/statecache-fill-watermark

Conversation

@yperbasis

Copy link
Copy Markdown
Member

Closes #22356 — the eviction-resurrection hole found by a Codex review of #22159 (#22159 (comment)).

Stacked on #22159 (yperbasis/statecache-review-fixes) — GitHub will retarget to main once it merges; only the last commit is this PR.

What changed

Applied-progress watermarkStateCache gains a per-domain atomic.Uint64 watermark of flush-applied commits: NoteApplied (CAS-max) is called by the flush cache-apply for every update it lands (an account deletion also notes CodeDomain for its no-code marker), Unwind lowers it to the unwind point, Clear zeroes it. Tiny and unevictable — the property the per-key tombstones lack.

Every addr-keyed fill proves snapshot freshness — the SD read-fill, the warmup getter and codeHashForAddr's addr→codeHash populate compare their own snapshot's DomainProgress against the watermark and skip the fill when behind. A pre-delete snapshot is by construction behind the delete-commit the cache absorbed, so its fills are rejected whether or not the tombstone survived eviction. Tombstones/markers remain the immediate-coherence fast path; the watermark is the backstop. The gate subsumes the warmup getter's nil-progress guard: a getter without a progress oracle cannot prove freshness and never fills.

Deliberately ungated — the content-addressed code-size fill (PutCodeSizeByHash): keyed by codeHash, whose size is immutable, so a stale snapshot cannot make it lie.

Cost — negatives already paid the keys-table LastKey (≈290 ns per #22159's benchmarks) for their stamp; it is now reused for the gate. Positive fills gain one such call, cold-miss-only. Exec, warmup and per-request RPC readers run on fresh snapshots and pass the gate unchanged.

Testing

TDD: red on the pre-fix code.

  • TestReadFill_DoesNotResurrectAfterMarkerEviction (accounts + code) — the Codex repro through the real read path: commit@10, pre-delete RO snapshot, delete@20, pressure-evict the marker, straddling fill; a fresh read returned the deleted value before, empty after.
  • TestCachePopulatingGetterStaleSnapshotDoesNotFill — the warmup-getter gate in isolation.
  • TestStateCache_AppliedProgressWatermark — the watermark follows applies up (monotonically), unwinds down, and Clear to zero, per domain.
  • TestCachePopulatingGetterNilProgressNeverFills — no oracle → no fills at all (previously: negatives only).

Verification: go build ./...; execution/cache(/coherence), db/state/execctx, execution/exec, execution/types/accounts suites, with -race on the first two; execution/tests and execution/execmodule short suites plus the reorg engine tests (TestReorgLongBlocks, TestChainTxReorgs, TestLongerForkBlocks); make lint clean.

…atermark gates stale-snapshot cache fills

A deletion tombstone or no-code marker is an ordinary LRU entry: cache
pressure can evict it while a pre-delete snapshot is still alive, and
that snapshot's read-fill then finds the key absent and resurrects the
deleted value as a live cache hit. Per-key defenses cannot close this —
pinning has no bounded lifetime and a stamp cannot distinguish a deleted
old value from a live unchanged one.

StateCache gains a per-domain applied-progress watermark — tiny,
unevictable — raised by the flush cache-apply, lowered by Unwind,
zeroed by Clear. Every addr-keyed fill (SD read-fill, warmup getter,
addr→codeHash populate) compares its own snapshot's DomainProgress
against it and skips when behind, so a stale snapshot cannot fill
regardless of the marker's fate. Subsumes the warmup getter's
nil-progress guard: no oracle, no fills.
yperbasis added a commit that referenced this pull request Jul 14, 2026
Stack the tombstone fix on the tombstone-free StateCache follow-ups so it uses their atomic PutIfAbsent read-fill ordering.

Fold #22357's applied-progress watermark into this branch. Stale RPC and warmup snapshots now skip fills even after cache pressure evicts a deletion marker; production-path integration coverage exercises account and code resurrection after eviction.
@yperbasis

Copy link
Copy Markdown
Member Author

Folded into #22444, which is now stacked on #22159 and contains both the deletion markers and the applied-progress watermark with production-path eviction coverage. Closing this superseded draft; its branch history was not rewritten.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant